<?php
/**
 * Created by Administrator
 * User: longli
 * VX: isa1589518286
 * Date: 2016/08/28
 * Time: 21:03
 * @link http://www.lmterp.cn
 */

namespace app\common\library;

class Tools
{
    /**
     * 检测字符串是否出现在另一个字符串的开头
     * @param string $str1 参照的字符串
     * @param string $str2 出现的字符串
     * @param bool $isCase 是否区分大小写，默认区分
     * @return boolean 返回布尔值
     * @author longli
     */
    public static function startWith($str1, $str2, $isCase = true)
    {
        $method = $isCase === true ? 'strpos' : 'stripos';
        return call_user_func_array($method, [$str1, $str2]) === 0;
    }

    /**
     * 检测字符串是否出现在另一个字符串的结尾
     * @param string $str1 参照的字符串
     * @param string $str2 出现的字符串
     * @param bool $isCase 是否区分大小写，默认区分
     * @return boolean 返回布尔值
     * @author longli
     */
    public static function endWith($str1, $str2, $isCase = true)
    {
        $method = $isCase === true ? 'strrpos' : 'strripos';
        return call_user_func_array($method, [$str1, $str2]) === strlen($str1) - strlen($str2);
    }

    /**
     * 检查字符串是否包含另一个字体串
     * @param string $str1 参照字符串
     * @param string $str2 被包含的字符串
     * @param bool $isCase 是否区分大小写
     * @return bool
     * @date   2020/06/19
     * @author longli
     */
    public static function contains($str1, $str2, $isCase = true)
    {
        $method = $isCase === true ? 'strpos' : 'stripos';
        return call_user_func_array($method, [$str1, $str2]) !== false;
    }

    /**
     * 下划线转驼峰命名
     * @param string $str 要转换的字符串
     * @return string 返回转换后的字符串
     * @author longli
     */
    public static function toCamelCase($str = "")
    {
        return preg_replace_callback('/_([a-z])/i', function($item) {
            return mb_strtoupper($item[1]);
        }, $str);
    }

    /**
     * 驼峰命名转下划线
     * @param string $str 要转换的字符串
     * @return string 返回转换后的字符串
     * @author longli
     */
    public static function toUnderScore($str = "")
    {
        return preg_replace_callback('/[A-Z]/', function($item) {
            return "_" . mb_strtolower($item[0]);
        }, $str);
    }

    /**
     * 无限极分类生成树
     * @param array $items 二维数组
     * @param string $pid 父级下标的，默认为pid
     * @param string $id 主键下标id
     * @param bool $useId 是否使用 id 做下标，默认不是
     * @return array 返回排序好的二维数组
     * @author longli
     */
    public static function generateTree(array $items, $id = 'id', $pid = 'pid', $useId = false)
    {
        $items = self::arraySetKey($items, $id);
        $tree = [];
        foreach($items as $item)
        {
            if(isset($items[$item[$pid]]))
            {
                $useId
                    ? $items[$item[$pid]]['son'][$item[$id]] = &$items[$item[$id]]
                    :  $items[$item[$pid]]['son'][] = &$items[$item[$id]];
            }
            else
            {
                $useId
                    ? $tree[$item[$id]] = &$items[$item[$id]]
                    : $tree[] = &$items[$item[$id]];
            }
        }
        return $tree;
    }

    /**
     * 生成无限分类的层次
     * @param array $array 已经排好的无限分类
     * @param string $son 字类的名称
     * @param int $level 当前的层次，默认为第一层
     * @return array 返回二维数组
     * @author longli
     */
    public static function levelArray(array $array, $son = 'son', $level = 0)
    {
        $ret = [];
        foreach($array as $key => $value)
        {
            $keys = array_flip(array_diff(array_keys($value), [$son]));
            $ret[] = array_merge(array_intersect_key($value, $keys), compact('level'));
            if(isset($value[$son]))
            {
                $t = self::levelArray($value[$son], $son, $level + 1);
                $ret = array_merge($ret, $t);
            }
        }
        return $ret;
    }

    /**
     * 获取随机字符串
     * @param int $len 要获取的长度，默认为6
     * @param int $index 获取哪种类型，0字母数字混合，1数字，2字母，默认为0
     * @param array $filter 需求过虑哪些字符
     * @return string 返回生成的字符串
     * @author longli
     */
    public static function getRandStr($len = 6, $index = 0, array $filter = [])
    {
        $list = [
            0 => 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz',
            1 => '0123456789',
            2 => 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
        ];
        if(!in_array($index, [1, 2]))
            $index = 0;
        $str = $list[$index];
        $max = strlen($str) - 1;
        $ret = '';
        for($i = 0; $i < $len; $i++)
        {
            $temp = $str[mt_rand(0, $max)];
            if(!empty($filter))
            {
                if(is_numeric($temp)) $temp = intval($temp);
                if(in_array($temp, $filter, true))
                {
                    $i--;
                    continue;
                }
            }
            $ret .= $temp;
        }
        return $ret;
    }

    /**
     * 二维数组排序
     * @param array $arr 要排序的二维数组
     * @param string $k 用哪个字段来排序
     * @param int $order 倒排还是正排，默认为正排
     * @param boolean $falg 是否保留原有键的顺序，默认为不保留
     * @return array 返回排序后的二维数组
     * @author longli
     */
    public static function sortArray($arr, $k, $order = SORT_ASC, $falg = false)
    {
        $temp = $ret = [];
        foreach($arr as $key => $value)
        {
            $temp[$key] = $value[$k];
        }

        $order == SORT_ASC ? arsort($temp) : asort($temp);

        foreach(array_keys($temp) as $tk => $key)
        {
            $ret[$falg ? $key : $tk] = $arr[$key];
        }
        return $ret;
    }

    /**
     * 二维数组按多个字段排序
     * @return false|array
     * @date 2021/07/18
     * @author longli
     * @example sortArrayByManyField($array, 'id', SORT_ASC, 'name', SORT_ASC, 'age', SORT_DESC)
     */
    public static function sortArrayByManyField()
    {
        $args = func_get_args();
        if(empty($args)) false;
        $array = array_shift($args);
        // 第一个参数必须为数组
        if(!is_array($array)) return false;
        foreach($args as $key => $field)
        {
            if(!is_string($field)) continue;
            $args[$key] = array_column($array, $field);
        }
        $args[] = &$array; // 引用数组
        call_user_func_array('array_multisort', $args);
        return end($args);
    }

    /**
     * 验证两个无序数组是否相等
     * @param array $a1 数组1
     * @param array $a2 数组2
     * @return bool
     * @date 2021/07/02
     * @author longli
     */
    public static function equalsArray(array $a1, array $a2)
    {
        sort($a1);
        sort($a2);
        return $a1 == $a2;
    }

    /**
     * 检测是否使用手机访问
     * @return bool
     * @author longli
     */
    public static function isMobile()
    {
        return (isset($_SERVER['HTTP_VIA']) && stristr($_SERVER['HTTP_VIA'], "wap"))
            ||
            (isset($_SERVER['HTTP_ACCEPT']) && strpos(strtoupper($_SERVER['HTTP_ACCEPT']), "VND.WAP.WML"))
            ||
            (isset($_SERVER['HTTP_X_WAP_PROFILE']) || isset($_SERVER['HTTP_PROFILE']))
            ||
            (isset($_SERVER['HTTP_USER_AGENT']) && preg_match('/(blackberry|configuration\/cldc|hp |hp-|htc |htc_|htc-|iemobile|kindle|midp|mmp|motorola|mobile|nokia|opera mini|opera |Googlebot-Mobile|YahooSeeker\/M1A1-R2D2|android|iphone|ipod|mobi|palm|palmos|pocket|portalmmm|ppc;|smartphone|sonyericsson|sqh|spv|symbian|treo|up.browser|up.link|vodafone|windows ce|xda |xda_)/i', $_SERVER['HTTP_USER_AGENT']));
    }

    /**
     * 检查是不是一个合法的手机号码
     * @param $phone
     * @return bool
     * @author longli
     */
    public static function isPhone($phone)
    {
        return !!preg_match("/^1[34578]{1}\d{9}$/", $phone);
    }

    /**
     * 检测是不是一个合法的邮箱
     * @param string $email 邮箱号码
     * @return bool
     * @author longli
     */
    public static function isEmail($email)
    {
        $regex = '/^([0-9A-Za-z\\-_\\.]+)@([0-9a-z]+\\.[a-z]{2,3}(\\.[a-z]{2})?)$/i';
        return !!preg_match($regex, $email);
    }

    /**
     * 重新为二维数组生成下标
     * @param array $array 二维数组
     * @param string|array|callable $callback 可传字符串，数组，回调函数，
     *                                        说明，字符串：在二维中的下标。数组：多个在二维数组中的下标，数组的最后一个为分隔符
     *                                        回调函数：通过传一个数组和一个下标，来重新生成一个key
     * @return array 返回整理好下标的数组
     * @author longli
     */
    public static function arraySetKey(array $array, $callback = '')
    {
        $ret = [];
        if(is_array($callback) && !is_callable($callback))
            $end = count($callback) > 1 ? array_pop($callback) : '_';
        foreach($array as $key => $value)
        {
            //if($callback instanceof \Closure)
            if(is_callable($callback))
            {
                $newKey = call_user_func($callback, $value, $key);
            }
            elseif(is_array($callback))
            {
                $s = '';
                foreach($callback as $v)
                    if(isset($value[$v]))
                        $s .= $value[$v] . $end;
                $newKey = rtrim($s, $end);
            }
            else
            {
                $newKey = isset($value[$callback]) ? $value[$callback] : current($value);
            }
            $ret[$newKey] = $value;
        }
        return $ret;
    }

    /**
     * 替换数组下标
     * @param array $replace 要替换的下标关联数组
     * @param array $array 被替换的数组
     * @return array 替换结果
     * @author longli
     */
    public static function replaceArrayKey(array $replace, array $array)
    {
        $ret = [];
        foreach($array as $key => $value)
        {
            $nk = array_key_exists($key, $replace) ? $replace[$key] : $key;
            if(is_array($value))
                $ret[$nk] = self::replaceArrayKey($replace, $value);
            else
                $ret[$nk] = $value;
        }
        return $ret;
    }

    /**
     * 显示或者隐藏数组
     * @param array $stack 指定要显示或者隐藏的下标
     * @param array $array 关联数组
     * @param bool $isVisble 指定为显示还是隐藏，默认为显示
     * @return array 处理后的结果
     * @author longli
     */
    public static function visibleArray(array $stack = [], array $array = [], $isVisble = true)
    {
        $ret = [];
        foreach($array as $key => $value)
        {
            if(is_array($value))
            {
                $t = self::visibleArray($stack, $value, $isVisble);
                if(!empty($t)) $ret[$key] = $t;
            }
            else
            {
                if($isVisble ? in_array($key, $stack, true) : !in_array($key, $stack, true))
                    $ret[$key] = $value;
            }
        }
        /*if(count($array) == count($array, COUNT_RECURSIVE))
        {
          $stack = array_flip($stack);
          $ret = $isVisble ? array_intersect_key($array, $stack) : array_diff_key($array, $stack);
        }
        else
        {
          foreach($array as $key => $value)
          {
            if(is_array($value))
            {
              $t = self::visibleArray($stack, $value, $isVisble);
              if(!empty($t))
                $ret[$key] = $t;
            }
          }
        }
       */
        return $ret;
    }

    /**
     * 找出数组的区间值
     * @param array $array 二维数组
     * @param int $search 要找的区间值
     * @param string $tkey 用哪个下标来做搜索条件
     * @return array 返回找到的数组，找不到返回最后一条
     * @author longli
     */
    public static function searchArrayInterval(array $array, $search, $tkey)
    {
        $array = self::sortArray($array, $tkey);
        $ret = end($array);
        foreach($array as $value)
        {
            if($value[$tkey] >= $search)
            {
                $ret = $value;
                break;
            }
        }
        return $ret;
    }

    /**
     * 判断变量为空，并且不是数字
     * @param mixed $value 需要检查的变量
     * @return bool
     * @date 2020/09/24
     * @author longli
     */
    public static function isEmpty($value)
    {
        return empty($value) && !is_numeric($value);
    }

    /**
     * 检测是否为https请求
     * @param string $url 要检测的url
     * @return bool 返回检测结果
     * @author longli
     */
    public static function isHttps($url)
    {
        return self::startWith($url, 'https://');
    }

    /**
     * 判断是不是ajax请求
     * @return boolean
     * @author longli
     */
    public static function isAjax()
    {
        return isset($_SERVER['HTTP_X_REQUESTED_WITH']) &&
            mb_strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest';
    }

    /**
     * 判断是否为post请求
     * @return bool 返回是否为post请求
     * @author longli
     */
    public static function isPost()
    {
        return isset($_SERVER['REQUEST_METHOD']) && mb_strtoupper(trim($_SERVER['REQUEST_METHOD'])) == 'POST';
    }

    /**
     * 判断是否为json格式
     * @param string $json 要检查的json字符串
     * @param null $ret 是否要接收转换的json
     * @return bool 返回是否为json格式
     * @author longli
     */
    public static function isJson($json, &$ret = null)
    {
        $ret = json_decode($json, true);
        return json_last_error() === JSON_ERROR_NONE;
    }

    /**
     * 判断是否为正确的时间格式
     * @param string $date 日期时间
     * @param null $ret 是否需要接收返回的 unix 时间戳
     * @return bool
     * @date 2020/09/28
     * @author longli
     */
    public static function isDate($date, &$ret = null)
    {
        $ret = strtotime($date);
        return $ret !== false;
    }

    /**
     * 通过curl发送get请求
     * @param string $url 请求的url
     * @param array $data 请求的参数
     * @param array $header 设置请求头
     * @param int $timeOut 设置过期时间，默认为30秒
     * @return array 返回请求结果，失败status为false，成功status为true
     * @author longli
     */
    public static function curlGet($url, array $data = [], array $header = [], $timeOut = 30)
    {
        $queryString = "";
        if(!empty($data))
        {
            $queryString .= strpos($url, '?') !== false ? '&' : '?';
            $queryString .= http_build_query($data);
        }
        $ch = curl_init();
        $setopt = [
            CURLOPT_URL => $url . $queryString,
            CURLOPT_TIMEOUT => $timeOut,
            CURLOPT_RETURNTRANSFER => true
        ];
        if(self::isHttps($url))
        {
            $setopt[CURLOPT_SSL_VERIFYPEER] = false;
            $setopt[CURLOPT_SSL_VERIFYHOST] = false;
        }
        if(!empty($header))
            $setopt += [CURLOPT_HTTPHEADER => $header];
        curl_setopt_array($ch, $setopt);
        $output = curl_exec($ch);
        $info = curl_getinfo($ch);
        curl_close($ch);
        return ["data" => $output, "info" => $info, "status" => in_array($info['http_code'], [200, 301, 302])];
    }

    /**
     * 通过curl发送post请求
     * @param string $url 请求的url
     * @param string|array $data 如果为如果数组，则为普通的post请求，如果要发送json或者字符串请先转换格式
     * @param array $header 设置请求头
     * @param int $timeOut 设置过期时间，默认为30秒
     * @return array 返回请求结果，失败status为false，成功status为true
     * @author longli
     */
    public static function curlPost($url, $data = '', array $header = [], $timeOut = 30)
    {
        if(is_array($data))
            $data = http_build_query($data);
        $ch = curl_init();
        $setopt = [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => $timeOut,
            CURLOPT_POST => 1,
            CURLOPT_POSTFIELDS => $data,
            CURLOPT_HEADER => 0,
        ];
        if(self::isHttps($url))
        {
            $setopt[CURLOPT_SSL_VERIFYPEER] = false;
            $setopt[CURLOPT_SSL_VERIFYHOST] = false;
        }
        if(is_string($data)) array_push($header, "Content-Length:" . strlen($data));
        if(self::isJson($data)) array_push($header, "Content-Type:application/json");
        if(!empty($header)) $setopt[CURLOPT_HTTPHEADER] = $header;
        curl_setopt_array($ch, $setopt);
        $output = curl_exec($ch);
        $info = curl_getinfo($ch);
        curl_close($ch);
        return ["data" => $output, "info" => $info, "status" => in_array($info['http_code'], [200, 301, 302])];
    }

    /**
     * xml 转数组
     * @param string $xml 需要转换的xml格式或文件
     * @param string $xpath 指定 xpath 路径
     * @return mixed 返回转换结果
     * @date   2017-03-14
     * @author longli
     */
    public static function xmlToArray($xml, $xpath = '')
    {
        if(is_file($xml))
            $xml = file_get_contents($xml);
        $xmlObj = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
        if(!empty($xpath)) $xmlObj = $xmlObj->xpath($xpath);
        return json_decode(json_encode($xmlObj), true);
    }

    /**
     * 数组转xml
     * @param array $array 需要转换的数组
     * @param string|array $root 根节点的名称，默认为 root, 数组格式['name'=>'root', 'attribute'=>['k'=>'v']]
     * @param \DOMDocument $dom DOM文档，无需传值，默认为null
     * @param \DOMElement $item 节点，无需传值，默认为null
     * @param string $charset xml编码，默认为utf-8
     * @return string 转换后的xml格式
     * @date   2017-03-14
     * @author longli
     */
    public static function arrayToXml(array $array, $root = 'root', $dom = null, $item = null, $charset = 'utf-8')
    {
        if(!($dom instanceof \DOMDocument))
        {
            $dom = new \DOMDocument("1.0", $charset);
        }
        if(!($item instanceof \DOMElement))
        {
            if(is_array($root) && !empty($root['name']))
            {
                $item = $dom->createElement($root['name']);
                if(isset($root['attribute']))
                {
                    foreach($root['attribute'] as $key => $val)
                    {
                        $item->setAttribute($key, $val);
                    }
                }
            }
            else
            {
                if(empty($root))
                    $root = 'root';
                $item = $dom->createElement($root);
            }
            $dom->appendChild($item);
        }
        foreach($array as $key => & $val)
        {
            if((!is_string($key) && empty($item)) || self::startWith($key, 'attr@'))
                continue;
            if(is_numeric($key) && !empty($item))
            {
                self::arrayToXml($val, $root, $dom, $item);
            }
            else
            {
                $itemx = $dom->createElement($key);
                if(isset($array["attr@$key"]))
                {
                    $attrKey = "attr@$key";
                    $attrs = $array[$attrKey];
                    unset($array[$attrKey]);
                    foreach($attrs as $atk => $attr)
                    {
                        $itemx->setAttribute($atk, $attr);
                    }
                }
                $item->appendChild($itemx);
                if(!is_array($val) && !is_null($val))
                {
                    $text = is_numeric($val) ? $dom->createTextNode($val) : $dom->createCDATASection($val);
                    $itemx->appendChild($text);
                }
                else
                {
                    if(!empty($val))
                    {
                        self::arrayToXml($val, $root, $dom, $itemx);
                    }
                }
            }
        }
        return $dom->saveXML();
    }

    /**
     * 检查是不是一维数据
     * @param array $arr 要检测的数组
     * @return boolean 返回检测结果
     * @author longli
     */
    public static function arrayGetMaxdim(array $arr = [])
    {
        return count($arr) == count($arr, 1);
    }

    /**
     * 获取uuid
     * @return string 生成的uuid
     * @author longli
     */
    public static function uuid()
    {
        $chars = md5(uniqid(mt_rand(), true));
        $uuid = substr($chars, 0, 8);
        $uuid .= substr($chars, 8, 4);
        $uuid .= substr($chars, 12, 4);
        $uuid .= substr($chars, 16, 4);
        $uuid .= substr($chars, 20, 12);
        return $uuid;
    }

    /**
     * 将数组分割成多个来执行
     * @param callable $callback 回调函数
     * @param array $array 要处理的数组
     * @param int $num 分割的大小
     * @return void
     * @author longli
     */
    public static function chunk(callable $callback, array $array = [], $num = 100)
    {
        $slice = array_chunk($array, $num);
        foreach($slice as $value)
        {
            if(call_user_func($callback, $value) === false)
                break;
        }
    }

    /**
     * 获取指定的月份有多少天
     * @param int $month 默认为当前月
     * @param int $year 默认为当前年
     * @return int 返回天数
     * @author longli
     */
    public static function getDaysByMonth($month = null, $year = null)
    {
        $m = intval($month > 0 && $month < 13 ? $month : date('m'));
        $y = intval(!is_numeric($year) ? date('Y') : $year);
        return date('t', strtotime("$y-$m"));
    }

    /**
     * 获取最近几个年份并排好序
     * @param int $num 最近的年份，默认为1年
     * @param string $order 排序，默认倒排
     * @return array
     * @date 2021/07/13
     * @author longli
     */
    public static function getRecentYear($num = 1, $order = 'desc')
    {
        if($num < 1) $num = 1;
        $curYear = date('Y');
        $year = range($curYear - $num, $curYear);
        $order == 'asc'
            ? sort($year)
            : rsort($year);
        return $year;
    }

    /**
     * 分割文件为指定的行数，只支持普通文件
     * @param string $file 要分割的文件
     * @param string $outputPath 输出目录, 默认为当前目录
     * @param int $line 要分割的行数,默认为 10000行
     * @param int $len 指定分割后缀数字的长度，默认为2位
     * @return bool 是否分割成功
     * @date 2020/05/25
     * @author longli
     */
    public static function splitFileLine($file, $outputPath = './', $line = 10000, $len = 2)
    {
        // 参数校验
        if(!is_dir($outputPath)) mkdir($outputPath, 0777, true);
        if(!is_file($file) || !is_writable($outputPath)) return false;
        // 获取文件的前后缀
        $suffix = strrchr($file, '.');
        $prefix = substr($file, 0, -strlen($suffix));
        // 打开文件
        $inFile = fopen($file, "r");
        // 得到要分割的文件总数
        // $totalFile = ceil(filesize($file) / $line);
        // 创建计数器, 遍历分割文件
        $current = 0;
        $index = 1;
        while(!feof($inFile))
        {
            if($current == $line)
            {
                $current = 0;
                $index++;
            }
            $current++;
            $pad = str_pad($index , $len, 0, STR_PAD_LEFT);
            $newName = "$outputPath/{$prefix}-{$pad}{$suffix}";
            if($current == 1 && is_file($newName)) @unlink($newName);
            file_put_contents($newName, fgets($inFile), FILE_APPEND);
        }
        // 关闭文件流
        fclose($inFile);
        return true;
    }

    /**
     * 分割文件为指定的大小,支持二进制文件
     * @param string $file 要分割的文件
     * @param string $outputPath 输出目录, 默认为当前目录
     * @param int $size 要分割的大小,默认为 5M
     * @param int $len 指定分割后缀数字的长度，默认为2位
     * @return bool 是否分割成功
     * @date 2020/05/25
     * @author longli
     */
    public static function splitFileSize($file, $outputPath = './', $size = 5, $len = 2)
    {
        // 参数校验
        if(!is_dir($outputPath)) mkdir($outputPath, 0777, true);
        if(!is_file($file) || !is_writable($outputPath)) return false;
        // 处理分割大小
        $splitSize = 1048576 * $size;
        $inFile = fopen($file, "rb");
        // 获取文件的前后缀
        $suffix = strrchr($file, '.');
        $prefix = substr($file, 0, -strlen($suffix));
        // 遍历分割文件
        $index = 0;
        while(!feof($inFile))
        {
            $index++;
            $pad = str_pad($index , $len, 0, STR_PAD_LEFT);
            file_put_contents("$outputPath/{$prefix}-{$pad}{$suffix}", fread($inFile, $splitSize));
        }
        // 关闭文件流
        fclose($inFile);
        return true;
    }

    /**
     * 合并文件
     * @param string|array $files 要合并的文件
     * @param string $newFileName 输出的文件名,默认为当前时间
     * @param string $outputPath 输出的路径,默认为当前路径
     * @return bool 是否合并成功
     * @date 2020/05/25
     * @author longli
     */
    public static function mergeFile($files, $newFileName = '', $outputPath = './')
    {
        // 参数校验
        if(!is_dir($outputPath)) mkdir($outputPath, 0777, true);
        if(!is_writable($outputPath))return false;
        if(!is_array($files)) $files = glob($files);
        if(!is_array($files) || empty($files)) return false;
        if(empty($newFileName)) $newFileName = date('YmdHis');
        sort($files);
        // 打开文件流
        $mergeFp = fopen("$outputPath/$newFileName", "wb");
        // 遍历要合并的文件
        foreach($files as $file)
        {
            if(!is_file($file)) continue;
            fwrite($mergeFp, file_get_contents($file));
        }
        // 关闭文件流
        fclose($mergeFp);
        return true;
    }

    /**
     * 获取文件后缀名
     * @param string $file 文件或者文件夹
     * @date 2020/07/31
     * @author longli
     * @return bool|string|array
     */
    public static function getFileSuffix($file = "")
    {
        return substr(strrchr($file, '.'), 1);
    }

    /**
     * 随机重命名文件
     * @param string $file 文件或者文件夹
     * @date 2020/07/31
     * @author longli
     * @return bool|string|array
     */
    public static function rename($file)
    {
        if(!file_exists($file)) return false;
        if(is_file($file))
        {
            $newFile = dirname($file) . "/" . self::getRandStr(mt_rand(16, 22)) . strrchr( $file,'.');
            rename($file, $newFile);
            return $newFile;
        }
        $newFile = [];
        if(is_dir($file))
        {
            $fp = opendir($file);
            while($f = readdir($fp))
            {
                if(in_array($f, ['.', '..'])) continue;
                $newFile[] = self::rename("$file/$f");
            }
            closedir($fp);
        }
        return $newFile;
    }

    /**
     * 复制文件夹
     * @param string $source 源路径
     * @param string $toPath 目标路径
     * @date 2020/07/31
     * @author longli
     * @return bool
     */
    public static function copyDir($source, $toPath)
    {
        if(!is_dir($source)) return false;
        if(!is_dir($toPath)) mkdir($toPath, 0777, true);      //创建文件夹
        $fp = opendir($source);
        while(($fileName = readdir($fp)) !== false)
        {
            if(in_array($fileName, ['.', '..'])) continue;
            $subFile = "$source/$fileName";
            if(is_file($subFile))
            {
                $b = "$toPath/$fileName";
                copy($subFile, $b);
            }
            else if(is_dir($subFile))
            {
                self::copyDir($subFile, "$toPath/$fileName");
            }
        }
        closedir($fp);
        return true;
    }

    /**
     * 获取指定路径下的所有文件
     * @param string $path 路径
     * @return array 返回指定目录下的所有文件
     * @date   2017/07/13
     * @author longli
     */
    public static function getFiles($path)
    {
        if(!self::endWith($path, '*'))
            $path = rtrim($path, '/') . '/*';
        $glob = glob($path);
        if($glob === false)
            return [];
        return array_filter($glob, function($item) {
            return filetype($item) == 'file';
        });
    }

    /**
     * 递归删除文件
     * @param string $path 指定要删除的路径
     * @return bool
     * @date 2020/04/17
     * @author longli
     */
    public static function deleteFiles($path)
    {
        if(!is_writable($path) || !file_exists($path)) return false;
        if(is_file($path))
        {
            unlink($path);
            return true;
        }
        if(is_dir($path))
        {
            $fp = opendir($path);
            while(($file = readdir($fp)) !== false)
            {
                if(in_array($file, ['.', '..'])) continue;
                $pathName = "$path/$file";
                is_dir($pathName)
                    ? self::deleteFiles($pathName)
                    : unlink($pathName);
            }
            closedir($fp);
            rmdir($path);
        }
        return true;
    }

    /**
     * 替换规则字符串为英文逗号
     * @param string|array $str 要替换的字符串
     * @param array $rule 替换的规则，如果为空则从配置文件获取
     * @return string|array 返回替换后的结果
     * @author longli
     */
    public static function replaceRule($str, array $rule = [])
    {
        if(is_array($str))
        {
            return array_map(function($item) use ($rule) {
                return self::replaceRule($item, $rule);
            }, $str);
        }
        else
        {
            return str_replace($rule, ',', $str);
        }
    }

    /**
     * 递归去除左右空白字符
     * @param array|string $data 要处理的数组或字符串
     * @return array|string
     * @author longli
     */
    public static function trim($data)
    {
        return is_array($data)
            ? array_map([self::class, __FUNCTION__], $data)
            : trim($data);
    }

    /**
     * 统计单词出现次数
     * @param string $str 单词句子
     * @param string $delimiter 分隔符号，默认为空格
     * @param int $order 排序，默认倒序，可选 (desc, asc), false 保持原有输出
     * @return array
     * @date   2018/07/14
     * @author longli
     */
    public static function wordCount($str = '', $delimiter = ' ', $order = SORT_DESC)
    {
        $ret = [];
        empty($delimiter) && $delimiter = ' ';
        foreach(array_filter(explode($delimiter, $str)) as $v)
        {
            !isset($ret[$v]) && $ret[$v] = 0;
            $ret[$v]++;
        }
        $order == SORT_ASC
            ? asort($ret)
            : arsort($ret);
        return $ret;
    }

    /**
     * 把字符串转为utf-8编码
     * @param array|string $str 要转换的字符串
     * @param string $encode 要转换的编码，默认为GBK
     * @return array|string 转换后的字符串
     * @author longli
     */
    public static function toUTF8($str, $encode = 'GBK')
    {
        if(is_array($str))
        {
            return array_map(function($item) use ($encode) {
                return self::toUTF8($item, $encode);
            }, $str);
        }
        else
        {
            return trim($encode) == 'UTF-8'
                ? $str
                : mb_convert_encoding($str, 'UTF-8', $encode);
        }
    }

    /**
     * 使用二进制的方法，把多个字段存储到一个字段，如一个产品有多个状态
     * <p>$total = 7; $status = [1, 2, 4, 8, 16, 32];</p>
     * @param int $total 总态之和
     * @param array $array 所有的状态
     * @return array 返回所包涵的状态
     * @date   2018/09/16
     * @author longli
     */
    public static function statusIsIn($total, array $array = [])
    {
        $ret = [];
        foreach($array as $item)
        {
            if(($total & $item) == $item)
            {
                $ret[] = $item;
                $total -= $item;
            }
            if($total <= 0)
                break;
        }
        return $ret;
    }

    /**
     * 查询数组里的值
     * @param  string $strKey 字符下标，多个使用"."来分开，如:(a.b.c)
     * @param array $array 要查看的数据
     * @return mixed|null 返回值，如找不到则返回 null
     * @date   2018/09/16
     * @author longli
     */
    public static function findVlaByArray($strKey, array $array = [])
    {
        $ret = null;
        foreach(explode('.', self::trim($strKey)) as $item)
        {
            $ret = isset($array[$item])
                ? $array = $array[$item]
                : null;
            if($ret === null)
                break;
        }
        return $ret;
    }

    /**
     * 解析 url 的请求参数为数组
     * @param string $url url
     * @return array 返回关联数组
     * @date   2018/11/22
     * @author longli
     */
    public static function parseUrlQueryToArray($url)
    {
        $ret = [];
        $parse = parse_url($url);
        if(!empty($parse['query']))
        {
            foreach(explode('&', $parse['query']) as $item)
            {
                list($k, $v) = explode('=', $item);
                $ret[trim($k)] = trim($v);
            }
        }
        return $ret;
    }

    /**
     * 检查数据组是否完全包含另一个数组
     * @param array $a1 需要检查的数组
     * @param array $a2 被包含的数据
     * @param bool $flag 是否带 KV 检查， 默认带
     * @return bool
     * @date   2019/01/24
     * @author longli
     */
    public static function containArray(array $a1, array $a2, $flag = true)
    {
        if($flag) return array_intersect_key($a1, $a2) == $a2;
        return count(array_intersect($a1, $a2)) >= count($a2);
    }

    /**
     * 去掉时间的时分秒
     * @param string|int $time unix 时间戳或者字符串时间
     * @return int|string
     * @date   2019/12/06
     * @author longli
     */
    public static function parseTime($time)
    {
        return is_numeric($time)
            ? strtotime(date('Y-m-d', $time))
            : date('Y-m-d', strtotime($time));
    }

    /**
     * 给指定日期添加指定的天数
     * @param int $day 需要添加的天数
     * @param int $time 时间戳， 默认为当前时间
     * @return false|string
     * @date 2020/12/20
     * @author longli
     */
    public static function addDate($day, $time = null)
    {
        if(empty($time)) $time = time();
        if(!is_numeric($time)) $time = strtotime($time);
        return date('Y-m-d', strtotime("{$day}day", $time));
    }

    /**
     * 获取两个时间相差的天数
     * @param string $day1 时间1
     * @param string $day2 时间2
     * @return int
     * @date 2020/12/20
     * @author longli
     */
    public static function diffDay($day1, $day2)
    {
        if(!is_numeric($day1)) $day1 = strtotime($day1);
        if(!is_numeric($day2)) $day2 = strtotime($day2);
        return intval(($day1 - $day2) / 86400);
    }

    /**
     * 检测字符串中是否有连续的数字
     * @param string $str 需要检测的字符串
     * @param int $total 检查连续的次数，默认为 3， 取值必须大于 1
     * <p>例如：abc123 连续的次数为3次，abc124为两次</p >
     * @return bool
     * @date 2020/02/25
     * @author longli
     */
    public static function checkSeqInt($str, $total = 3)
    {
        if($total < 2 || strlen($str) < 1) return false;
        $ret = false;
        $len = strlen($str) - 1;
        $index = 0;
        $count = 1;
        while($index < $len)
        {
            if(!is_numeric($str[$index]) || !is_numeric($str[$index + 1]))
            {
                $index++;
                $count = 1;
                continue;
            }
            $current = $str[$index];
            $next = $str[$index + 1];
            $count += $next - $current == 1
                ? 1
                : -$count + 1;
            if($count == $total)
            {
                $ret = true;
                break;
            }
            $index++;
        }
        return $ret;
    }

    /**
     * 获取当前时间
     * @param int $time 指定时间戳，默认为当前
     * @date 2020/04/15
     * @author longli
     */
    public static function now($time = null)
    {
        return date('Y-m-d H:i:s', $time?:time());
    }

    /**
     * 检查字符串是否包含中文
     * @param string $str
     * @date 2020/07/06
     * @author longli
     * @return bool
     */
    public static function isContainChinese($str)
    {
        return !!preg_match("/[\x7f-\xff]+/", $str);
    }

    /**
     * 获取图片长宽及大小kb
     * @param string $filePath 图片路径
     * @return array|bool 成功返回图片信息，失败返回 false
     * @date 2020/08/15
     * @author longli
     */
    public static function getImageInfo($filePath)
    {
        if(!is_file($filePath) || ($imgInfo = @getimagesize($filePath)) === false) return false;
        list($width, $height) = $imgInfo;
        $size = round(filesize($filePath) / 1024, 2);
        return compact('width', 'height', 'size');
    }

    /**
     * 判断运行环境是否为命令行模式
     * @return bool
     * @date 2020/09/04
     * @author longli
     */
    public static function isCli()
    {
        return php_sapi_name() == 'cli';
    }

    /**
     * 清空数组值
     * @param array $array 要处理的数组
     * @return array
     * @date 2020/09/11
     * @author longli
     */
    public static function emptyArray(array $array = [])
    {
        return array_map(function(){return "";}, $array);
    }

    /**
     * 递归返回数组值
     * @param array $array
     * @return array
     * @date 2020/09/13
     * @author longli
     */
    public static function arrayValues(array $array, $all = true)
    {
        $ret = [];
        foreach($array as $k => $v)
        {

            $sv = is_array($v)
                ? self::arrayValues($v, $all)
                : $v;
                $all ? $ret[] = $sv
                     : (is_numeric($k) ? $ret[] = $sv : $ret[$k] = $sv);
        }
        return $ret;
    }

    /**
     * 获取不带命名空间类名
     * @param string $class 全类名
     * @return string
     * @date 2020/09/24
     * @author longli
     */
    public static function classBasename($class)
    {
        return basename(str_replace('\\', '/', $class));
    }
}

?>
